/*
 * \brief  Core implementation of the RAM session interface
 * \author Norman Feske
 * \date   2006-05-19
 */

/*
 * Copyright (C) 2006-2009 Norman Feske
 * Genode Labs, Feske & Helmuth Systementwicklung GbR
 *
 * This file is part of the Genode OS framework, which is distributed
 * under the terms of the GNU General Public License version 2.
 */

#include <base/printf.h>
#include <util/arg_string.h>

#include "ram_session_component.h"

using namespace Genode;


/* DEBUGGING */
static bool verbose = false;


void Ram_session_component::_free_ds(Dataspace_component *ds)
{
	if (!ds) return;

	size_t ds_size = ds->size();

	/* destroy native shared memory representation */
	_revoke_ram_ds(ds);

	/* tell entry point to forget the dataspace */
	_ds_ep->dissolve(ds);

	/* free physical memory that was backing the dataspace */
	_ram_alloc->free((void *)ds->phys_addr(), ds_size);

	/* call dataspace destructors and free memory */
	destroy(&_ds_slab, ds);

	/* adjust payload */
	_payload -= ds_size;
}


int Ram_session_component::_transfer_quota(Ram_session_component *dst, size_t amount)
{
	/* check if recipient is a valid Ram_session_component */
	if (!dst) return -1;

	/* check for reference account relationship */
	if ((ref_account() != dst) && (dst->ref_account() != this))
		return -3;

	/* decrease quota limit of this session - check against used quota */
	if (_quota_limit < amount + _payload) {
		PWRN("Ram_session_component::transfer_quota: insufficient free quota on source");
		PWRN("                                       have %d, need %d",
		     _quota_limit - _payload, amount);
		return -3;
	}

	_quota_limit -= amount;

	/* increase quota_limit of recipient */
	dst->_quota_limit += amount;

	return 0;
}


void Ram_session_component::_register_ref_account_member(Ram_session_component *new_member)
{
	Lock::Guard lock_guard(_ref_members_lock);
	_ref_members.insert(new_member);
	new_member->_ref_account = this;
}


void Ram_session_component::_unsynchronized_remove_ref_account_member(Ram_session_component *member)
{
	member->_ref_account = 0;
	_ref_members.remove(member);
}


void Ram_session_component::_remove_ref_account_member(Ram_session_component *member)
{
	Lock::Guard lock_guard(_ref_members_lock);
	_unsynchronized_remove_ref_account_member(member);
}


Ram_dataspace_capability Ram_session_component::alloc(size_t ds_size)
{
	/* dataspace allocation granularity is page size */
	ds_size = align_addr(ds_size, 12);

	/*
	 * Check quota!
	 *
	 * In the worst case, we need to allocate a new slab block for the
	 * meta data of the dataspace to be created - therefore, we add
	 * the slab block size here.
	 */
	if (used_quota() + SBS + ds_size >= _quota_limit) {

		PWRN("quota exceeded!");
		PWRN("  memory for slab:               %d", _ds_slab.consumed());
		PWRN("  used quota:                    %d", used_quota());
		PWRN("  ds_size:                       %d", ds_size);
		PWRN("  sizeof(Ram_session_component): %d", sizeof(Ram_session_component));
		PWRN("  quota_limit:                   %d", _quota_limit);

		throw Quota_exceeded();
	}

	void *ds_addr = 0;
	if (!_ram_alloc->alloc_aligned(ds_size, &ds_addr, 12)) {

		/*
		 * Normally, the Init's quota equals the size of physical memory
		 * and this quota is distributed among the processes.
		 * As we check the quota before allocating, the allocation
		 * should always succeed in theory. However, fragmentation
		 * could cause a failing allocation.
		 */
		PERR("We ran out of physical memory while allocating %d bytes", ds_size);
		throw Quota_exceeded();
	}

	/* fill new dataspaces with zeros */
	memset(ds_addr, 0, ds_size);

	Dataspace_component *ds;
	ds = new (&_ds_slab) Dataspace_component(ds_size, (addr_t)ds_addr);

	if (!ds) {
		PWRN("Could not allocate metadata");
		throw Quota_exceeded();
	}

	/* keep track of the used quota for actual payload */
	_payload += ds_size;

	if (verbose)
		PDBG("ds_size=%d, used_quota=%d quota_limit=%d",
		     ds_size, used_quota(), _quota_limit);

	Ram_dataspace_capability result = Ram_dataspace_capability(_ds_ep->manage(ds));

	/* create native shared memory representation of dataspace */
	_export_ram_ds(ds);

	return result;
}


void Ram_session_component::free(Ram_dataspace_capability ds_cap)
{
	_free_ds(dynamic_cast<Dataspace_component *>(_ds_ep->obj_by_cap(ds_cap)));
}


int Ram_session_component::ref_account(Ram_session_capability ram_session_cap)
{
	/* the reference account cannot be defined twice */
	if (_ref_account) return -2;

	Ram_session_component *ref = dynamic_cast<Ram_session_component *>
	                            (_ram_session_ep->obj_by_cap(ram_session_cap));

	/* check if recipient is a valid Ram_session_component */
	if (!ref) return -1;

	/* deny the usage of the ram session as its own ref account */
	/* XXX also check for cycles along the tree of ref accounts */
	if (ref == this) return -3;

	_ref_account = ref;
	_ref_account->_register_ref_account_member(this);
	return 0;
}


int Ram_session_component::transfer_quota(Ram_session_capability ram_session_cap,
                                          size_t amount)
{
	if (verbose)
		PDBG("amount=%d", amount);
	Ram_session_component *dst = dynamic_cast<Ram_session_component *>
	                            (_ram_session_ep->obj_by_cap(ram_session_cap));

	return _transfer_quota(dst, amount);
}


Ram_session_component::Ram_session_component(Server_entrypoint *ds_ep,
                                             Server_entrypoint *ram_session_ep,
                                             Range_allocator   *ram_alloc,
                                             const char        *args,
                                             size_t             quota_limit)
:
	_ds_ep(ds_ep), _ram_session_ep(ram_session_ep), _ram_alloc(ram_alloc),
	_quota_limit(quota_limit), _payload(0), _ds_slab(ram_alloc), _ref_account(0)
{ }


Ram_session_component::~Ram_session_component()
{
	/* destroy all dataspaces */
	for (Dataspace_component *ds; (ds = _ds_slab.first_object()); _free_ds(ds));

	if (_payload != 0)
		PWRN("Remaining payload of %d in ram session to destroy", _payload);

	if (!_ref_account) return;

	/* transfer remaining quota to reference account */
	_transfer_quota(_ref_account, _quota_limit);

	/* remember our original reference account */
	Ram_session_component *orig_ref_account = _ref_account;

	/* remove reference to us from the reference account */
	_ref_account->_remove_ref_account_member(this);

	/*
	 * Now, the '_ref_account' member has become invalid.
	 */

	Lock::Guard lock_guard(_ref_members_lock);

	/* assign all sub accounts to our original reference account */
	for (Ram_session_component *rsc; (rsc = _ref_members.first()); ) {

		_unsynchronized_remove_ref_account_member(rsc);

		/*
		 * This function grabs the '_ref_account_lock' of the '_ref_account',
		 * which is never identical to ourself. Hence, deadlock cannot happen
		 * here.
		 */
		orig_ref_account->_register_ref_account_member(rsc);
	}

	_ref_account = 0;
}
